package com.doctcloud.datacloud.service;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.doctcloud.common.core.domain.R;
import com.doctcloud.datacloud.entity.PriceLastestEntity;
import com.doctcloud.datacloud.entity.PriceListEntity;
import com.doctcloud.datacloud.mapper.PriceListEntityMapper;
import com.doctcloud.hisinfo.api.RemoteHisCommonService;
import com.doctcloud.hisinfo.histable.domain.comm.PriceListDo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.doctcloud.common.core.utils.DateUtils.setDate;
import static com.doctcloud.common.core.utils.StringUtils.extractChineseCharacters;

/**
 * 价表信息服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PriceListEntityService extends ServiceImpl<PriceListEntityMapper, PriceListEntity> {

    private final RemoteHisCommonService remoteHisCommonService;
    private final PriceLastestEntityService priceLastestEntityService;
    @Value("${sync.batch.size:1000}")
    private int BATCH_SIZE;

    /**
     * 价表信息同步（分批全量同步）
     * 优化点：
     * 1. 解决 N+1 查询问题，通过批量查询提升性能。
     * 2. 简化逻辑，将 PriceLastest 的更新操作批量处理。
     * 3. 保持事务一致性，确保主表和快照表的操作原子性。
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncPriceList() {
        // ... 此方法内容保持不变 ...
        log.info("--- 开始同步价表数据 ---");
        long startTime = System.currentTimeMillis();
        int totalAffectedRows = 0;

        try {
            // 步骤1: 从远程服务获取全量价表信息
            log.info("正在从远程服务获取全量价表信息...");
            R<List<PriceListDo>> remotePriceListR = remoteHisCommonService.dbPriceListAll();

            if (remotePriceListR == null || !R.isSuccess(remotePriceListR) || CollectionUtils.isEmpty(remotePriceListR.getData())) {
                String errorMsg = (remotePriceListR != null) ? remotePriceListR.getMsg() : "远程服务响应为null";
                log.error("从远程服务获取价表列表失败或返回为空，同步任务终止。原因: {}", errorMsg);
                return;
            }

            List<PriceListDo> remotePriceLists = remotePriceListR.getData();
            log.info("从远程服务获取到 {} 条价表记录，准备进行转换和分批处理。", remotePriceLists.size());

            // 步骤2: 将 RemotePriceListDo 列表转换为 PriceListEntity 列表
            List<PriceListEntity> entitiesToUpsert = remotePriceLists.stream()
                    .map(this::convertToEntity)
                    .collect(Collectors.toList());

            if (CollectionUtils.isEmpty(entitiesToUpsert)) {
                log.info("没有需要同步的价表信息，任务结束。");
                return;
            }

            // 步骤3: 分批执行 UPSERT 操作和更新最新价表快照
            log.info("开始分批执行同步操作，每批 {} 条记录。", BATCH_SIZE);

            for (int i = 0; i < entitiesToUpsert.size(); i += BATCH_SIZE) {
                int end = Math.min(i + BATCH_SIZE, entitiesToUpsert.size());
                List<PriceListEntity> batchEntities = entitiesToUpsert.subList(i, end);
                log.debug("正在处理第 {} 批数据，批次大小: {}。", (i / BATCH_SIZE) + 1, batchEntities.size());

                // 3.1: 批量 Upsert 到主价表
                int affectedRows = baseMapper.batchUpsert(batchEntities);
                totalAffectedRows += affectedRows;
                log.debug("第 {} 批主表 Upsert 完成，本批影响行数: {}", (i / BATCH_SIZE) + 1, affectedRows);

                // 3.2: 批量处理 '最新价表' 快照
                this.batchUpdatePriceLastest(batchEntities);
                log.debug("第 {} 批最新价表快照更新完成。", (i / BATCH_SIZE) + 1);
            }

            log.info("--- 价表数据同步全部完成 ---");
            log.info("总耗时: {} ms。主表总受影响的行数: {}",
                    (System.currentTimeMillis() - startTime), totalAffectedRows);

        } catch (Exception e) {
            log.error("同步价表数据时发生严重异常: ", e);
            throw new RuntimeException("价表信息同步任务失败，请检查日志并联系管理员。", e);
        }
    }

    /**
     * 批量更新最新价表快照。
     * 此方法通过一次查询获取批次内所有相关的现有记录，然后在内存中进行比对和处理，
     * 最后调用 batchSaveOrUpdate 执行批量的保存或更新操作，从而避免了 N+1 查询问题。
     */
    private void batchUpdatePriceLastest(List<PriceListEntity> batchEntities) {
        if (CollectionUtils.isEmpty(batchEntities)) {
            return;
        }

        // 1. 构建复合查询条件：将批次内所有实体的唯一标识收集起来
        Set<PriceLastestEntity.Key> compositeKeys = batchEntities.stream()
                .map(entity -> new PriceLastestEntity.Key(
                        entity.getItemClass(),
                        entity.getItemCode(),
                        entity.getItemSpec(),
                        entity.getUnits()
                ))
                .collect(Collectors.toSet());

        // 2. 一次查询，获取数据库中已存在的所有相关记录
        List<PriceLastestEntity> existingEntities = priceLastestEntityService.listByCompositeKeys(new ArrayList<>(compositeKeys));

        // 3. 将查询结果转换为 Map，便于快速查找
        Map<PriceLastestEntity.Key, PriceLastestEntity> existingEntityMap = existingEntities.stream()
                .collect(Collectors.toMap(
                        entity -> new PriceLastestEntity.Key(
                                entity.getItemClass(),
                                entity.getItemCode(),
                                entity.getItemSpec(),
                                entity.getUnits()
                        ),
                        entity -> entity
                ));

        List<PriceLastestEntity> entitiesToSaveOrUpdate = new ArrayList<>();

        // 4. 在内存中遍历、比对和处理
        for (PriceListEntity entity : batchEntities) {
            PriceLastestEntity.Key key = new PriceLastestEntity.Key(
                    entity.getItemClass(),
                    entity.getItemCode(),
                    entity.getItemSpec(),
                    entity.getUnits()
            );

            PriceLastestEntity existingEntity = existingEntityMap.get(key);

            if (existingEntity == null) {
                // 4.1: 不存在，则创建新记录
                PriceLastestEntity newEntity = new PriceLastestEntity();
                newEntity.setItemClass(entity.getItemClass());
                newEntity.setItemCode(entity.getItemCode());
                newEntity.setItemName(entity.getItemName());
                newEntity.setItemSpec(entity.getItemSpec());
                newEntity.setUnits(entity.getUnits());
                newEntity.setPrice(entity.getPrice());
                newEntity.setForeignerPrice(entity.getForeignerPrice());
                newEntity.setPreferPrice(entity.getPreferPrice());
                newEntity.setUpdateDate(entity.getStartDate());
                entitiesToSaveOrUpdate.add(newEntity);
            } else {
                // 4.2: 已存在，则检查是否需要更新
                if (entity.getStartDate().after(existingEntity.getUpdateDate())) {
                    existingEntity.setUpdateDate(entity.getStartDate());
                    existingEntity.setPrice(entity.getPrice());
                    existingEntity.setForeignerPrice(entity.getForeignerPrice());
                    existingEntity.setPreferPrice(entity.getPreferPrice());
                    existingEntity.setItemName(entity.getItemName()); // 名称可能会变
                    entitiesToSaveOrUpdate.add(existingEntity);
                }
            }
        }

        // 5. 调用新的批量保存或更新方法
        if (!entitiesToSaveOrUpdate.isEmpty()) {
            priceLastestEntityService.batchSaveOrUpdate(entitiesToSaveOrUpdate);
        }
    }

    /**
     * 将 PriceListDo 转换为 PriceListEntity。
     */
    private PriceListEntity convertToEntity(PriceListDo priceDo) {
        // ... 此方法内容保持不变 ...
        PriceListEntity entity = new PriceListEntity();
        entity.setItemClass(priceDo.getItemClass());
        entity.setItemCode(priceDo.getItemCode());
        entity.setItemName(extractChineseCharacters(priceDo.getItemName()));
        entity.setItemSpec(priceDo.getItemSpec());
        entity.setUnits(priceDo.getUnits());
        entity.setPrice(Optional.ofNullable(priceDo.getPrice()).orElse(0.0));
        entity.setPreferPrice(Optional.ofNullable(priceDo.getPreferPrice()).orElse(0.0));
        entity.setForeignerPrice(Optional.ofNullable(priceDo.getForeignerPrice()).orElse(0.0));
        entity.setStartDate(Optional.ofNullable(priceDo.getStartDate()).orElse(setDate(2001, 1, 1, 9, 1, 1)));
        entity.setStopDate(priceDo.getStopDate());
        entity.setEnterDate(new Date());
        return entity;
    }
}